package org.apache.dolphinscheduler.plugin.task.flinksql;

import com.fasterxml.jackson.databind.node.ObjectNode;
import org.apache.commons.io.FileUtils;
import org.apache.dolphinscheduler.plugin.task.api.AbstractYarnTask;
import org.apache.dolphinscheduler.plugin.task.api.ShellCommandExecutor;
import org.apache.dolphinscheduler.plugin.task.util.MapUtils;
import org.apache.dolphinscheduler.plugin.task.util.OSUtils;
import org.apache.dolphinscheduler.spi.task.AbstractParameters;
import org.apache.dolphinscheduler.spi.task.Property;
import org.apache.dolphinscheduler.spi.task.paramparser.ParamUtils;
import org.apache.dolphinscheduler.spi.task.paramparser.ParameterUtils;
import org.apache.dolphinscheduler.spi.task.request.TaskRequest;
import org.apache.dolphinscheduler.spi.utils.JSONUtils;
import org.apache.dolphinscheduler.spi.utils.StringUtils;

import java.io.File;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardOpenOption;
import java.nio.file.attribute.FileAttribute;
import java.nio.file.attribute.PosixFilePermission;
import java.nio.file.attribute.PosixFilePermissions;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;

import static org.apache.dolphinscheduler.spi.task.TaskConstants.RWXR_XR_X;

public class FlinksqlTask extends AbstractYarnTask {

    /**
     * flinkx command
     * <p>
     * bin/flinkx \
     * -mode local \
     * -jobType sql \
     * -jobName flink1.12_SPI
     * -job flinkx-local-test/src/main/demo/json/stream/stream.sql \
     * -pluginRoot flinkxplugins
     */
    private static final String Flinksql_COMMAND = "bin/flinkx";

    /**
     * FlinksqlParameters parameters
     */
    private FlinksqlParameters flinksqlParameters;

    /**
     * taskExecutionContext
     */
    private TaskRequest taskExecutionContext;

    private String flinkxHome;


    private ShellCommandExecutor shellCommandExecutor;

    public FlinksqlTask(TaskRequest taskExecutionContext) {
        super(taskExecutionContext);
        this.taskExecutionContext = taskExecutionContext;
        //TODO SS local模式
        this.shellCommandExecutor = new ShellCommandExecutor(this::logHandle,
                taskExecutionContext, logger);
    }

    @Override
    public void init() {

        logger.info("flink task params {}", taskExecutionContext.getTaskParams());
        flinkxHome = System.getenv("FLINKX_HOME");
        flinksqlParameters = JSONUtils.parseObject(taskExecutionContext.getTaskParams(), FlinksqlParameters.class);
        //TODO SS 通哥调整
        if(OSUtils.isWindows()){
            flinkxHome = "D:\\david\\david_source\\flinkx_chunjun\\chunjun-1.12.1";
        }
        if(StringUtils.isEmpty(flinkxHome)){
            throw new RuntimeException("FLINKX_HOME不能为空");
        }
        if (null == flinksqlParameters) {
            logger.error("flinksql params is null");
            return;
        }

        if (!flinksqlParameters.checkParameters()) {
            throw new RuntimeException("flink task params is not valid");
        }
    }


    @Override
    public String buildCommand() {
        String shellCommandFilePath = "";
        try {

            // replace placeholder,and combine local and global parameters
            Map<String, Property> paramsMap = ParamUtils.convert(taskExecutionContext, getParameters());
            if (MapUtils.isEmpty(paramsMap)) {
                paramsMap = new HashMap<>();
            }
            if (MapUtils.isNotEmpty(taskExecutionContext.getParamsMap())) {
                paramsMap.putAll(taskExecutionContext.getParamsMap());
            }

            // run Flinksql procesDataSourceService.s
            String jsonFilePath = buildFlinksqlJsonFile(paramsMap);
            shellCommandFilePath = buildShellCommandFile(jsonFilePath, paramsMap);
        } catch (Exception e) {
            logger.error("Flinksql build cmd error :{} ", e);
        }
        return shellCommandFilePath;
    }

    @Override
    protected void setMainJarName() {

    }

    private String buildFlinksqlJsonFile(Map<String, Property> paramsMap)
            throws Exception {
        // generate json
        String fileName = String.format("%s/%s_job.json",
                taskExecutionContext.getExecutePath(),
                taskExecutionContext.getTaskAppId());
        String sql = flinksqlParameters.getSql().replaceAll("\\r\\n", "\n");

        Path path = new File(fileName).toPath();
        if (Files.exists(path)) {
            return fileName;
        }


        // replace placeholder
        sql = ParameterUtils.convertParameterPlaceholders(sql, ParamUtils.convert(paramsMap));

        logger.debug("Flinksql job sql : {}", sql);

        // create Flinksql json file
        FileUtils.writeStringToFile(new File(fileName), sql, StandardCharsets.UTF_8);
        return fileName;
    }

    private String buildShellCommandFile(String jobConfigFilePath, Map<String, Property> paramsMap)
            throws Exception {
        // generate scripts
        String fileName = String.format("%s/%s_node.%s",
                taskExecutionContext.getExecutePath(),
                taskExecutionContext.getTaskAppId(),
                OSUtils.isWindows() ? "bat" : "sh");

        Path path = new File(fileName).toPath();

        if (Files.exists(path)) {
            return fileName;
        }

        // Flinksql  command
        StringBuilder sbr = new StringBuilder();
        sbr.append(getFlinksqlCommand());
        sbr.append(FlinksqlConstants.BLANK);
        sbr.append(FlinksqlConstants.MODE);
        sbr.append(FlinksqlConstants.BLANK);
        sbr.append(FlinksqlConstants.FLINKSQL_ON_YARN.equals(flinksqlParameters.getDeployMode()) ? flinksqlParameters.getDeployMode() : FlinksqlConstants.FLINKSQL_LOCAL);
        sbr.append(FlinksqlConstants.BLANK);
        sbr.append(FlinksqlConstants.JOB_NAME);
        sbr.append(FlinksqlConstants.BLANK);
        sbr.append("flinksql" + System.currentTimeMillis());
        sbr.append(FlinksqlConstants.BLANK);
        sbr.append(FlinksqlConstants.JOB);
        sbr.append(FlinksqlConstants.BLANK);
        sbr.append(jobConfigFilePath);
        sbr.append(FlinksqlConstants.BLANK);
        sbr.append(FlinksqlConstants.PLUGIN_ROOT);
        sbr.append(FlinksqlConstants.BLANK);
        sbr.append(flinkxHome + "/"+FlinksqlConstants.FLINKSQL_PLUGINS);
        sbr.append(FlinksqlConstants.BLANK);
        sbr.append(FlinksqlConstants.CONF_PROP);
        sbr.append(FlinksqlConstants.BLANK);
        sbr.append(getFlinksqlConfig());


        // replace placeholder
        String FlinksqlCommand = ParameterUtils.convertParameterPlaceholders(sbr.toString(), ParamUtils.convert(paramsMap));

        logger.debug("raw script : {}", FlinksqlCommand);

        // create shell command file
        Set<PosixFilePermission> perms = PosixFilePermissions.fromString(RWXR_XR_X);
        FileAttribute<Set<PosixFilePermission>> attr = PosixFilePermissions.asFileAttribute(perms);

        if (OSUtils.isWindows()) {
            Files.createFile(path);
        } else {
            Files.createFile(path, attr);
        }

        Files.write(path, FlinksqlCommand.getBytes(), StandardOpenOption.APPEND);

        return fileName;
    }


    public String getFlinksqlCommand() {
        return flinkxHome + "/" + Flinksql_COMMAND + " -jobType sql";
    }

    public String getFlinksqlConfig() {
        ObjectNode configJson = JSONUtils.createObjectNode();
        configJson.put("flink.checkpoint.interval",60000);
        configJson.put("jobmanager.memory.process.size","1600m");
        configJson.put("taskmanager.memory.process.size","1728m");
        configJson.put("taskmanager.numberOfTaskSlots",1);
        configJson.put("sql.env.parallelism",1);
        String configStr =  FlinksqlConstants.MARK +configJson.toString().replace("\"","\\\"") + FlinksqlConstants.MARK;
        logger.info("Flinksql configStr: {}", configStr);
        return configStr;
    }


    @Override
    public AbstractParameters getParameters() {
        return flinksqlParameters;
    }
}


